Перейти к основному содержимому

Шаблоны простых элементов веб-страниц

Разработчику Архитектору

Шаблоны простых элементов веб-страниц

Простые элементы веб-страниц составляют основу любого пользовательского интерфейса. Они обеспечивают взаимодействие пользователя с системой, позволяют вводить данные, просматривать информацию и управлять приложением. Эти компоненты универсальны: их можно встретить на сайтах электронной коммерции, в личных кабинетах, административных панелях, формах обратной связи и множестве других контекстов.

Настоящая глава представляет собой справочник по наиболее часто используемым шаблонам HTML-элементов, дополненных базовой стилизацией через CSS и минимальной серверной логикой на PHP. Все примеры написаны с учётом современных стандартов веб-разработки, семантической корректности и доступности (accessibility). Каждый шаблон автономен, легко копируется и адаптируется под конкретные задачи.


Форма входа (Login Form)

Форма входа — один из самых распространённых элементов веб-интерфейсов. Она позволяет пользователю аутентифицироваться в системе с использованием логина (или email) и пароля.

Структура HTML

<form id="loginForm" method="POST" action="login.php" novalidate>
<h2>Вход в систему</h2>
<div class="form-group">
<label for="email">Электронная почта</label>
<input type="email" id="email" name="email" required autocomplete="email">
</div>
<div class="form-group">
<label for="password">Пароль</label>
<input type="password" id="password" name="password" required autocomplete="current-password">
</div>
<div class="form-group form-check">
<input type="checkbox" id="remember" name="remember">
<label for="remember">Запомнить меня</label>
</div>
<button type="submit">Войти</button>
<p class="form-footer">
<a href="forgot-password.php">Забыли пароль?</a> |
<a href="register.php">Создать аккаунт</a>
</p>
</form>

Базовый CSS

#loginForm {
max-width: 400px;
margin: 2rem auto;
padding: 2rem;
border: 1px solid #ddd;
border-radius: 8px;
background-color: #fafafa;
font-family: Arial, sans-serif;
}

#loginForm h2 {
text-align: center;
margin-bottom: 1.5rem;
color: #333;
}

.form-group {
margin-bottom: 1rem;
}

.form-group label {
display: block;
margin-bottom: 0.4rem;
font-weight: bold;
color: #444;
}

.form-group input[type="email"],
.form-group input[type="password"] {
width: 100%;
padding: 0.6rem;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 1rem;
box-sizing: border-box;
}

.form-check {
display: flex;
align-items: center;
gap: 0.5rem;
}

.form-check input[type="checkbox"] {
margin: 0;
}

button[type="submit"] {
width: 100%;
padding: 0.8rem;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.2s;
}

button[type="submit"]:hover {
background-color: #0056b3;
}

.form-footer {
text-align: center;
margin-top: 1rem;
font-size: 0.9rem;
}

.form-footer a {
color: #007bff;
text-decoration: none;
}

.form-footer a:hover {
text-decoration: underline;
}

Пример обработки на PHP (login.php)

<?php
session_start();

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = filter_var($_POST['email'] ?? '', FILTER_SANITIZE_EMAIL);
$password = $_POST['password'] ?? '';
$remember = isset($_POST['remember']);

// Валидация
if (empty($email) || empty($password)) {
die('Все поля обязательны для заполнения.');
}

if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
die('Некорректный email.');
}

// Здесь должна быть проверка в базе данных
// Пример условного логина:
if ($email === 'user@example.com' && $password === 'secret123') {
$_SESSION['user_email'] = $email;
if ($remember) {
setcookie('user_email', $email, time() + 30 * 24 * 3600, '/');
}
header('Location: dashboard.php');
exit;
} else {
echo 'Неверные учетные данные.';
}
}
?>

📌 Внимание
В реальных проектах пароли никогда не хранятся в открытом виде. Используйте password_hash() и password_verify() для безопасной работы с паролями.


Таблица с данными

Таблицы используются для отображения структурированной информации: списков пользователей, товаров, заказов и т.д.

HTML

<table class="data-table">
<caption>Список пользователей</caption>
<thead>
<tr>
<th scope="col">ID</th>
<th scope="col">Имя</th>
<th scope="col">Email</th>
<th scope="col">Роль</th>
<th scope="col">Действия</th>
</tr>
</thead>
<tbody>
<tr>
<td data-label="ID">1</td>
<td data-label="Имя">Анна Петрова</td>
<td data-label="Email">anna@example.com</td>
<td data-label="Роль">Администратор</td>
<td data-label="Действия">
<button class="btn btn-edit">Редактировать</button>
<button class="btn btn-delete">Удалить</button>
</td>
</tr>
<tr>
<td data-label="ID">2</td>
<td data-label="Имя">Иван Сидоров</td>
<td data-label="Email">ivan@example.com</td>
<td data-label="Роль">Пользователь</td>
<td data-label="Действия">
<button class="btn btn-edit">Редактировать</button>
<button class="btn btn-delete">Удалить</button>
</td>
</tr>
</tbody>
</table>

CSS

.data-table {
width: 100%;
border-collapse: collapse;
margin: 1.5rem 0;
font-size: 0.95rem;
text-align: left;
}

.data-table caption {
font-size: 1.2rem;
font-weight: bold;
margin-bottom: 0.5rem;
text-align: left;
}

.data-table th,
.data-table td {
padding: 0.75rem;
border-bottom: 1px solid #ddd;
}

.data-table th {
background-color: #f4f4f4;
font-weight: bold;
}

.data-table tbody tr:hover {
background-color: #f9f9f9;
}

.btn {
padding: 0.3rem 0.6rem;
margin-right: 0.3rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.85rem;
}

.btn-edit {
background-color: #28a745;
color: white;
}

.btn-delete {
background-color: #dc3545;
color: white;
}

/* Адаптивность для мобильных */
@media (max-width: 600px) {
.data-table,
.data-table thead,
.data-table tbody,
.data-table th,
.data-table td,
.data-table tr {
display: block;
}

.data-table thead tr {
position: absolute;
top: -9999px;
left: -9999px;
}

.data-table td {
position: relative;
padding-left: 50%;
border: none;
border-bottom: 1px solid #eee;
}

.data-table td:before {
content: attr(data-label) ": ";
position: absolute;
left: 6px;
width: 45%;
padding-right: 10px;
font-weight: bold;
}
}

Пагинация

Пагинация позволяет разбивать длинные списки на страницы.

HTML

<nav aria-label="Навигация по страницам">
<ul class="pagination">
<li><a href="?page=1" aria-label="Первая страница">&laquo;</a></li>
<li><a href="?page=2" aria-label="Предыдущая страница">&lsaquo;</a></li>
<li><a href="?page=1">1</a></li>
<li><a href="?page=2">2</a></li>
<li class="active"><span>3</span></li>
<li><a href="?page=4">4</a></li>
<li><a href="?page=5">5</a></li>
<li><a href="?page=4" aria-label="Следующая страница">&rsaquo;</a></li>
<li><a href="?page=10" aria-label="Последняя страница">&raquo;</a></li>
</ul>
</nav>

CSS

.pagination {
display: flex;
list-style: none;
padding: 0;
margin: 1.5rem 0;
justify-content: center;
gap: 0.25rem;
}

.pagination a,
.pagination span {
display: inline-block;
padding: 0.4rem 0.8rem;
text-decoration: none;
color: #007bff;
border: 1px solid #ddd;
border-radius: 4px;
transition: background-color 0.2s;
}

.pagination a:hover {
background-color: #e9ecef;
}

.pagination .active span {
background-color: #007bff;
color: white;
border-color: #007bff;
}

PHP-логика (пример)

<?php
$page = $_GET['page'] ?? 1;
$perPage = 10;
$totalItems = 123; // Например, из COUNT(*) в SQL
$totalPages = ceil($totalItems / $perPage);
$page = max(1, min($page, $totalPages));

$offset = ($page - 1) * $perPage;

// Запрос к БД с LIMIT и OFFSET
// SELECT * FROM users LIMIT $perPage OFFSET $offset
?>

Кнопки разных типов

Кнопки — ключевой элемент управления.

HTML

<div class="button-group">
<button class="btn-primary">Основная</button>
<button class="btn-secondary">Второстепенная</button>
<button class="btn-success">Успех</button>
<button class="btn-danger">Опасность</button>
<button class="btn-warning">Предупреждение</button>
<button class="btn-info">Информация</button>
<button class="btn-link">Ссылка</button>
</div>

CSS

.button-group {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
margin: 1.5rem 0;
}

.btn-primary,
.btn-secondary,
.btn-success,
.btn-danger,
.btn-warning,
.btn-info,
.btn-link {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: opacity 0.2s;
}

.btn-primary { background-color: #007bff; color: white; }
.btn-secondary { background-color: #6c757d; color: white; }
.btn-success { background-color: #28a745; color: white; }
.btn-danger { background-color: #dc3545; color: white; }
.btn-warning { background-color: #ffc107; color: #212529; }
.btn-info { background-color: #17a2b8; color: white; }
.btn-link {
background: none;
color: #007bff;
text-decoration: underline;
padding: 0;
}

.btn-primary:hover { opacity: 0.9; }
.btn-link:hover { text-decoration: none; }

Вкладки (Tabs)

Вкладки — это интерфейсный паттерн, позволяющий переключаться между различными секциями контента без перезагрузки страницы. Они улучшают навигацию и организацию информации.

HTML

<div class="tabs-container">
<ul class="tabs-list" role="tablist">
<li role="presentation">
<button id="tab1" class="tab-button active" role="tab" aria-selected="true" aria-controls="panel1">Общее</button>
</li>
<li role="presentation">
<button id="tab2" class="tab-button" role="tab" aria-selected="false" aria-controls="panel2">Настройки</button>
</li>
<li role="presentation">
<button id="tab3" class="tab-button" role="tab" aria-selected="false" aria-controls="panel3">Помощь</button>
</li>
</ul>

<div id="panel1" class="tab-panel" role="tabpanel" aria-labelledby="tab1">
<p>Это содержимое вкладки "Общее". Здесь может быть любая информация: текст, формы, таблицы и т.д.</p>
</div>
<div id="panel2" class="tab-panel hidden" role="tabpanel" aria-labelledby="tab2">
<p>Здесь находятся параметры настройки приложения или профиля пользователя.</p>
</div>
<div id="panel3" class="tab-panel hidden" role="tabpanel" aria-labelledby="tab3">
<p>Раздел помощи содержит инструкции, часто задаваемые вопросы и ссылки на поддержку.</p>
</div>
</div>

CSS

.tabs-container {
max-width: 800px;
margin: 2rem auto;
font-family: Arial, sans-serif;
}

.tabs-list {
display: flex;
list-style: none;
padding: 0;
margin: 0 0 1rem 0;
border-bottom: 2px solid #ddd;
}

.tab-button {
padding: 0.75rem 1.5rem;
background: none;
border: none;
cursor: pointer;
font-size: 1rem;
color: #555;
position: relative;
transition: color 0.2s;
}

.tab-button:hover {
color: #007bff;
}

.tab-button.active {
color: #007bff;
font-weight: bold;
}

.tab-button.active::after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
right: 0;
height: 3px;
background-color: #007bff;
}

.tab-panel {
padding: 1.5rem;
background-color: #fff;
border: 1px solid #ddd;
border-top: none;
}

.hidden {
display: none;
}

JavaScript (опционально, для интерактивности)

<script>
document.querySelectorAll('.tab-button').forEach(button => {
button.addEventListener('click', () => {
// Убираем активность со всех кнопок и панелей
document.querySelectorAll('.tab-button').forEach(b => b.classList.remove('active'));
document.querySelectorAll('.tab-panel').forEach(p => p.classList.add('hidden'));

// Делаем текущую вкладку активной
button.classList.add('active');
const panelId = button.getAttribute('aria-controls');
document.getElementById(panelId).classList.remove('hidden');

// Обновляем aria-selected
document.querySelectorAll('.tab-button').forEach(b => {
b.setAttribute('aria-selected', b === button ? 'true' : 'false');
});
});
});
</script>

📌 Внимание
Для доступности важно использовать ARIA-атрибуты (role, aria-selected, aria-controls, aria-labelledby). Это позволяет пользователям скринридеров корректно взаимодействовать с вкладками.


Простой калькулятор

Калькулятор — полезный элемент для демонстрации обработки пользовательского ввода и вычислений на стороне клиента или сервера.

HTML + PHP (серверная версия)

<form method="POST" action="calculator.php">
<h2>Калькулятор</h2>
<div class="form-group">
<input type="number" name="a" step="any" value="<?= htmlspecialchars($_POST['a'] ?? '') ?>" required placeholder="Первое число">
</div>
<div class="form-group">
<select name="op" required>
<option value="+" <?= ($_POST['op'] ?? '') === '+' ? 'selected' : '' ?>>+</option>
<option value="-" <?= ($_POST['op'] ?? '') === '-' ? 'selected' : '' ?>>−</option>
<option value="*" <?= ($_POST['op'] ?? '') === '*' ? 'selected' : '' ?></option>
<option value="/" <?= ($_POST['op'] ?? '') === '/' ? 'selected' : '' ?></option>
</select>
</div>
<div class="form-group">
<input type="number" name="b" step="any" value="<?= htmlspecialchars($_POST['b'] ?? '') ?>" required placeholder="Второе число">
</div>
<button type="submit">Вычислить</button>
<?php if ($_SERVER['REQUEST_METHOD'] === 'POST'): ?>
<?php
$a = $_POST['a'];
$b = $_POST['b'];
$op = $_POST['op'];
$result = null;
$error = null;

if (!is_numeric($a) || !is_numeric($b)) {
$error = 'Оба значения должны быть числами.';
} elseif ($op === '/' && $b == 0) {
$error = 'Деление на ноль невозможно.';
} else {
switch ($op) {
case '+': $result = $a + $b; break;
case '-': $result = $a - $b; break;
case '*': $result = $a * $b; break;
case '/': $result = $a / $b; break;
default: $error = 'Неизвестная операция.';
}
}
?>
<div class="calc-result">
<?php if ($error): ?>
<p style="color: #dc3545;"><?= htmlspecialchars($error) ?></p>
<?php else: ?>
<p><strong>Результат:</strong> <?= htmlspecialchars(number_format($result, 6, '.', '')) ?></p>
<?php endif; ?>
</div>
<?php endif; ?>
</form>

CSS для калькулятора

form h2 {
text-align: center;
margin-bottom: 1.5rem;
}

.form-group {
margin-bottom: 1rem;
}

.form-group input,
.form-group select {
width: 100%;
padding: 0.6rem;
font-size: 1rem;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}

.calc-result {
margin-top: 1.5rem;
padding: 1rem;
background-color: #f8f9fa;
border-left: 4px solid #007bff;
}

💡 Совет
Серверная реализация гарантирует работу даже при отключённом JavaScript. Для более сложных калькуляторов (например, с историей или научными функциями) лучше использовать клиентский JavaScript.


Поля ввода разных типов

Современный HTML предоставляет множество типов <input>, улучшающих UX и валидацию.

HTML

<form class="input-demo">
<div class="form-row">
<label>Текст: <input type="text" placeholder="Имя"></label>
</div>
<div class="form-row">
<label>Email: <input type="email" placeholder="user@example.com"></label>
</div>
<div class="form-row">
<label>Пароль: <input type="password" placeholder="••••••••"></label>
</div>
<div class="form-row">
<label>Телефон: <input type="tel" placeholder="+7 (999) 123-45-67"></label>
</div>
<div class="form-row">
<label>Дата: <input type="date"></label>
</div>
<div class="form-row">
<label>Число: <input type="number" min="0" max="100" step="1"></label>
</div>
<div class="form-row">
<label>Цвет: <input type="color" value="#007bff"></label>
</div>
<div class="form-row">
<label>Файл: <input type="file" accept="image/*"></label>
</div>
<div class="form-row">
<label>Диапазон: <input type="range" min="0" max="100" value="50"></label>
</div>
</form>

CSS

.input-demo {
max-width: 500px;
margin: 2rem auto;
padding: 1.5rem;
background: #fafafa;
border-radius: 8px;
}

.form-row {
margin-bottom: 1rem;
}

.form-row label {
display: block;
margin-bottom: 0.4rem;
font-weight: bold;
color: #333;
}

.form-row input {
width: 100%;
padding: 0.5rem;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 1rem;
box-sizing: border-box;
}

⚠️ Предупреждение
Типы email, url, number и другие обеспечивают базовую валидацию на клиенте, но никогда не заменяют серверную проверку. Злоумышленник может легко обойти клиентские ограничения.


Форма обратной связи

Форма обратной связи — стандартный элемент веб-сайта, позволяющий пользователю отправить сообщение владельцу ресурса. Она часто используется для запросов поддержки, предложений или жалоб.

HTML

<form id="feedbackForm" method="POST" action="send_feedback.php">
<h2>Обратная связь</h2>
<div class="form-group">
<label for="name">Ваше имя</label>
<input type="text" id="name" name="name" required>
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
</div>
<div class="form-group">
<label for="subject">Тема</label>
<select id="subject" name="subject" required>
<option value="">— Выберите тему —</option>
<td><option value="support">Техническая поддержка</option></td>
<td><option value="suggestion">Предложение</option></td>
<td><option value="complaint">Жалоба</option></td>
<td><option value="other">Другое</option></td>
</select>
</div>
<div class="form-group">
<label for="message">Сообщение</label>
<textarea id="message" name="message" rows="6" required></textarea>
</div>
<button type="submit">Отправить</button>
</form>

CSS

#feedbackForm {
max-width: 600px;
margin: 2rem auto;
padding: 2rem;
border: 1px solid #ddd;
border-radius: 8px;
background-color: #fafafa;
}

#feedbackForm h2 {
text-align: center;
margin-bottom: 1.5rem;
color: #333;
}

.form-group {
margin-bottom: 1.25rem;
}

.form-group label {
display: block;
margin-bottom: 0.4rem;
font-weight: bold;
color: #444;
}

.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 0.6rem;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 1rem;
box-sizing: border-box;
}

.form-group textarea {
resize: vertical;
min-height: 100px;
}

button[type="submit"] {
width: 100%;
padding: 0.8rem;
background-color: #28a745;
color: white;
border: none;
border-radius: 4px;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.2s;
}

button[type="submit"]:hover {
background-color: #218838;
}

PHP (send_feedback.php)

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = trim($_POST['name'] ?? '');
$email = filter_var($_POST['email'] ?? '', FILTER_SANITIZE_EMAIL);
$subject = $_POST['subject'] ?? '';
$message = trim($_POST['message'] ?? '');

// Валидация
if (empty($name) || empty($email) || empty($subject) || empty($message)) {
die('Все поля обязательны.');
}

if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
die('Некорректный email.');
}

if (!in_array($subject, ['support', 'suggestion', 'complaint', 'other'])) {
die('Недопустимая тема.');
}

// Здесь должна быть отправка письма или сохранение в БД
// Пример:
// mail('admin@example.com', "Обратная связь: $subject", $message, "From: $email");

echo '<p style="color: green; text-align: center;">Сообщение успешно отправлено!</p>';
}
?>

📌 Внимание
На продакшене используйте библиотеки вроде PHPMailer или SwiftMailer для надёжной отправки почты и защиты от спама. Обязательно добавьте защиту от CSRF и rate limiting.


Поиск по сайту

Простая форма поиска позволяет пользователю искать контент на сайте.

HTML

<form class="search-form" method="GET" action="search.php">
<div class="search-container">
<input type="text" name="q" placeholder="Поиск по сайту…" required>
<button type="submit">🔍</button>
</div>
</form>

CSS

.search-form {
display: flex;
justify-content: center;
margin: 2rem 0;
}

.search-container {
display: flex;
width: 100%;
max-width: 500px;
border: 1px solid #ccc;
border-radius: 24px;
overflow: hidden;
}

.search-container input {
flex: 1;
padding: 0.75rem 1rem;
border: none;
font-size: 1rem;
outline: none;
}

.search-container button {
background: #007bff;
color: white;
border: none;
padding: 0 1.25rem;
cursor: pointer;
font-size: 1.1rem;
}

.search-container button:hover {
background: #0056b3;
}

PHP (search.php)

<?php
$q = trim($_GET['q'] ?? '');
if (empty($q)) {
die('Введите поисковый запрос.');
}

// Пример простого поиска по массиву (в реальности — по БД)
$pages = [
['title' => 'Главная', 'content' => 'Добро пожаловать на сайт'],
['title' => 'О нас', 'content' => 'Мы занимаемся разработкой'],
['title' => 'Контакты', 'content' => 'Напишите нам']
];

$results = array_filter($pages, fn($page) =>
stripos($page['title'], $q) !== false ||
stripos($page['content'], $q) !== false
);
?>

<h2>Результаты поиска по запросу: "<?= htmlspecialchars($q) ?>"</h2>
<?php if (count($results)): ?>
<ul>
<?php foreach ($results as $page): ?>
<li><strong><?= htmlspecialchars($page['title']) ?></strong>: <?= htmlspecialchars($page['content']) ?></li>
<?php endforeach; ?>
</ul>
<?php else: ?>
<p>Ничего не найдено.</p>
<?php endif; ?>

💡 Совет
Для полноценного поиска используйте Elasticsearch, Sphinx или хотя бы FULLTEXT индексы в MySQL.


Карточка товара / элемент списка

Часто используется в интернет-магазинах, каталогах, блогах.

HTML

<article class="product-card">
<img src="product.jpg" alt="Название товара" loading="lazy">
<div class="product-info">
<h3>Название товара</h3>
<p class="price">1 990 ₽</p>
<p class="description">Краткое описание товара, его особенности и преимущества.</p>
<button class="btn btn-primary">В корзину</button>
</div>
</article>

CSS

.product-card {
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
background: white;
max-width: 300px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
transition: transform 0.2s;
}

.product-card:hover {
transform: translateY(-4px);
box-shadow: 0 4px 16px rgba(0,0,0,0.15);
}

.product-card img {
width: 100%;
height: 200px;
object-fit: cover;
display: block;
}

.product-info {
padding: 1rem;
}

.product-info h3 {
margin: 0 0 0.5rem 0;
font-size: 1.1rem;
color: #333;
}

.price {
font-weight: bold;
color: #d32f2f;
margin: 0 0 0.75rem 0;
font-size: 1.2rem;
}

.description {
color: #666;
margin: 0 0 1rem 0;
font-size: 0.95rem;
line-height: 1.4;
}

.btn-primary {
background: #1976d2;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
}

Страница профиля пользователя

Страница профиля — это персональное пространство, где отображаются данные о пользователе, его активность и настройки. Она часто включает аватар, контактную информацию, историю действий и возможность редактирования данных.

HTML

<div class="profile-page">
<div class="profile-header">
<img src="avatar.jpg" alt="Аватар пользователя" class="avatar" loading="lazy">
<h1>Анна Петрова</h1>
<p class="user-role">Администратор</p>
</div>

<div class="profile-section">
<h2>Контактная информация</h2>
<ul class="profile-info">
<li><strong>Email:</strong> anna@example.com</li>
<li><strong>Телефон:</strong> +7 (999) 123-45-67</li>
<li><strong>Регистрация:</strong> 15 января 2024</li>
</ul>
</div>

<div class="profile-section">
<h2>Действия</h2>
<ul class="activity-list">
<li>Изменил(а) пароль — 1 апреля 2026</li>
<li>Вошёл(ла) в систему — 3 апреля 2026</li>
<li>Создал(а) новую статью — 28 марта 2026</li>
</ul>
</div>

<div class="profile-actions">
<a href="edit-profile.php" class="btn btn-primary">Редактировать профиль</a>
<a href="logout.php" class="btn btn-secondary">Выйти</a>
</div>
</div>

CSS

.profile-page {
max-width: 800px;
margin: 2rem auto;
padding: 2rem;
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}

.profile-header {
text-align: center;
margin-bottom: 2rem;
}

.avatar {
width: 120px;
height: 120px;
border-radius: 50%;
object-fit: cover;
border: 3px solid #f0f0f0;
margin-bottom: 1rem;
}

.profile-header h1 {
font-size: 1.8rem;
color: #333;
margin: 0;
}

.user-role {
color: #666;
font-style: italic;
margin-top: 0.25rem;
}

.profile-section {
margin-bottom: 2rem;
}

.profile-section h2 {
font-size: 1.4rem;
margin-bottom: 1rem;
color: #222;
border-bottom: 1px solid #eee;
padding-bottom: 0.5rem;
}

.profile-info,
.activity-list {
list-style: none;
padding: 0;
}

.profile-info li,
.activity-list li {
padding: 0.5rem 0;
border-bottom: 1px solid #f5f5f5;
}

.profile-info strong {
color: #333;
}

.profile-actions {
display: flex;
gap: 1rem;
justify-content: center;
margin-top: 1.5rem;
}

.btn {
padding: 0.6rem 1.2rem;
text-decoration: none;
border-radius: 4px;
font-weight: bold;
transition: opacity 0.2s;
}

.btn-primary {
background-color: #007bff;
color: white;
}

.btn-secondary {
background-color: #6c757d;
color: white;
}

.btn:hover {
opacity: 0.9;
}

📌 Внимание
На продакшене аватары следует загружать через защищённые маршруты с проверкой MIME-типов и ограничением размера файла.


Модальное окно (Modal)

Модальные окна используются для показа важной информации, подтверждения действий или сбора данных без перехода на другую страницу.

HTML

<!-- Кнопка вызова -->
<button id="openModalBtn">Открыть модальное окно</button>

<!-- Само модальное окно -->
<div id="myModal" class="modal" role="dialog" aria-labelledby="modalTitle" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h2 id="modalTitle">Подтверждение действия</h2>
<button type="button" class="close" aria-label="Закрыть">&times;</button>
</div>
<div class="modal-body">
<p>Вы уверены, что хотите удалить этот элемент? Это действие нельзя отменить.</p>
</div>
<div class="modal-footer">
<button id="confirmBtn" class="btn btn-danger">Удалить</button>
<button id="cancelBtn" class="btn btn-secondary">Отмена</button>
</div>
</div>
</div>
</div>

CSS

.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
animation: fadeIn 0.3s;
}

.modal-dialog {
position: relative;
margin: 10% auto;
max-width: 500px;
animation: slideIn 0.3s;
}

.modal-content {
background: white;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0,0,0,0.2);
}

.modal-header {
padding: 1.25rem 1.5rem;
border-bottom: 1px solid #eee;
display: flex;
justify-content: space-between;
align-items: center;
}

.modal-header h2 {
margin: 0;
font-size: 1.4rem;
color: #333;
}

.close {
font-size: 1.8rem;
background: none;
border: none;
cursor: pointer;
color: #999;
}

.close:hover {
color: #333;
}

.modal-body {
padding: 1.5rem;
color: #555;
}

.modal-footer {
padding: 1rem 1.5rem;
border-top: 1px solid #eee;
display: flex;
justify-content: flex-end;
gap: 0.75rem;
}

@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}

@keyframes slideIn {
from { transform: translateY(-20px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}

JavaScript

<script>
const modal = document.getElementById('myModal');
const openBtn = document.getElementById('openModalBtn');
const closeBtn = document.querySelector('.close');
const cancelBtn = document.getElementById('cancelBtn');

function openModal() {
modal.style.display = 'block';
modal.setAttribute('aria-hidden', 'false');
}

function closeModal() {
modal.style.display = 'none';
modal.setAttribute('aria-hidden', 'true');
}

openBtn.addEventListener('click', openModal);
closeBtn.addEventListener('click', closeModal);
cancelBtn.addEventListener('click', closeModal);

// Закрытие по клику вне окна
window.addEventListener('click', (e) => {
if (e.target === modal) closeModal();
});
</script>

💡 Совет
Для доступности важно управлять фокусом: при открытии модального окна фокус должен перемещаться внутрь него, а при закрытии — возвращаться к вызвавшему элементу.


Форма регистрации

Форма регистрации позволяет новому пользователю создать аккаунт.

HTML

<form id="registerForm" method="POST" action="register.php" novalidate>
<h2>Регистрация</h2>
<div class="form-group">
<label for="regName">Имя</label>
<input type="text" id="regName" name="name" required minlength="2" maxlength="50">
</div>
<div class="form-group">
<label for="regEmail">Email</label>
<input type="email" id="regEmail" name="email" required>
</div>
<div class="form-group">
<label for="regPassword">Пароль</label>
<input type="password" id="regPassword" name="password" required minlength="8">
<small class="hint">Минимум 8 символов, включая цифру и заглавную букву.</small>
</div>
<div class="form-group">
<label for="regConfirm">Подтверждение пароля</label>
<input type="password" id="regConfirm" name="confirm" required>
</div>
<button type="submit">Зарегистрироваться</button>
<p class="form-footer">
Уже есть аккаунт? <a href="login.php">Войти</a>
</p>
</form>

CSS (частично совпадает с формой входа)

#registerForm {
max-width: 450px;
margin: 2rem auto;
padding: 2rem;
border: 1px solid #ddd;
border-radius: 8px;
background-color: #fafafa;
}

.hint {
display: block;
margin-top: 0.25rem;
font-size: 0.85rem;
color: #666;
}

PHP (register.php)

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = trim($_POST['name'] ?? '');
$email = filter_var($_POST['email'] ?? '', FILTER_SANITIZE_EMAIL);
$password = $_POST['password'] ?? '';
$confirm = $_POST['confirm'] ?? '';

// Валидация
if (empty($name) || empty($email) || empty($password) || empty($confirm)) {
die('Все поля обязательны.');
}

if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
die('Некорректный email.');
}

if ($password !== $confirm) {
die('Пароли не совпадают.');
}

if (strlen($password) < 8) {
die('Пароль должен содержать минимум 8 символов.');
}

// Проверка сложности пароля (пример)
if (!preg_match('/[A-Z]/', $password) || !preg_match('/[0-9]/', $password)) {
die('Пароль должен содержать хотя бы одну заглавную букву и одну цифру.');
}

// Проверка уникальности email (в реальном проекте — запрос к БД)
// if (emailExists($email)) { die('Пользователь с таким email уже существует.'); }

// Хеширование пароля
$hashedPassword = password_hash($password, PASSWORD_DEFAULT);

// Сохранение в БД
// insertUser($name, $email, $hashedPassword);

echo 'Регистрация успешна! <a href="login.php">Войдите</a>.';
}
?>

⚠️ Предупреждение
Никогда не храните пароли в открытом виде. Используйте password_hash() и password_verify().


Форма обратной связи с валидацией на стороне клиента

Для улучшения пользовательского опыта можно добавить базовую валидацию прямо в браузере, не дожидаясь ответа сервера. Это снижает нагрузку на сервер и делает взаимодействие более отзывчивым.

HTML + JavaScript

<form id="feedbackFormJS" novalidate>
<h2>Обратная связь (с клиентской проверкой)</h2>
<div class="form-group">
<label for="jsName">Ваше имя</label>
<input type="text" id="jsName" name="name" required minlength="2" maxlength="50">
<span class="error" id="nameError"></span>
</div>
<div class="form-group">
<label for="jsEmail">Email</label>
<input type="email" id="jsEmail" name="email" required>
<span class="error" id="emailError"></span>
</div>
<div class="form-group">
<label for="jsSubject">Тема</label>
<select id="jsSubject" name="subject" required>
<option value="">— Выберите тему —</option>
<option value="support">Техническая поддержка</option>
<option value="suggestion">Предложение</option>
<option value="complaint">Жалоба</option>
<option value="other">Другое</option>
</select>
<span class="error" id="subjectError"></span>
</div>
<div class="form-group">
<label for="jsMessage">Сообщение</label>
<textarea id="jsMessage" name="message" rows="6" required minlength="10"></textarea>
<span class="error" id="messageError"></span>
</div>
<button type="submit">Отправить</button>
</form>

CSS (дополнение)

.error {
display: block;
color: #dc3545;
font-size: 0.875rem;
margin-top: 0.25rem;
min-height: 1.2rem;
}

JavaScript

<script>
document.getElementById('feedbackFormJS').addEventListener('submit', function(e) {
e.preventDefault();
let valid = true;

// Очистка ошибок
document.querySelectorAll('.error').forEach(el => el.textContent = '');

// Проверка имени
const name = document.getElementById('jsName');
if (!name.value.trim() || name.value.length < 2) {
document.getElementById('nameError').textContent = 'Имя должно содержать минимум 2 символа.';
valid = false;
}

// Проверка email
const email = document.getElementById('jsEmail');
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email.value)) {
document.getElementById('emailError').textContent = 'Введите корректный email.';
valid = false;
}

// Проверка темы
const subject = document.getElementById('jsSubject');
if (!subject.value) {
document.getElementById('subjectError').textContent = 'Выберите тему обращения.';
valid = false;
}

// Проверка сообщения
const message = document.getElementById('jsMessage');
if (!message.value.trim() || message.value.length < 10) {
document.getElementById('messageError').textContent = 'Сообщение должно содержать минимум 10 символов.';
valid = false;
}

if (valid) {
alert('Форма отправлена! (В реальном проекте — AJAX-запрос)');
// Здесь можно отправить форму через fetch()
// fetch('send_feedback.php', { method: 'POST', body: new FormData(this) })
}
});
</script>

💡 Совет
Клиентская валидация — это удобство, а не защита. Всегда дублируйте проверки на сервере.


Простой список задач (To-Do List)

Элементарный интерфейс для управления персональными задачами.

HTML

<div class="todo-app">
<h2>Список задач</h2>
<form id="addTaskForm">
<input type="text" id="newTask" placeholder="Новая задача…" required>
<button type="submit">+</button>
</form>
<ul id="taskList">
<!-- Задачи будут добавляться сюда -->
</ul>
</div>

CSS

.todo-app {
max-width: 500px;
margin: 2rem auto;
padding: 1.5rem;
border: 1px solid #ddd;
border-radius: 8px;
background: white;
}

.todo-app h2 {
text-align: center;
margin-bottom: 1.25rem;
}

#addTaskForm {
display: flex;
gap: 0.5rem;
margin-bottom: 1.5rem;
}

#addTaskForm input {
flex: 1;
padding: 0.5rem;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 1rem;
}

#addTaskForm button {
padding: 0.5rem 1rem;
background: #28a745;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}

#taskList {
list-style: none;
padding: 0;
}

#taskList li {
display: flex;
align-items: center;
padding: 0.75rem 0;
border-bottom: 1px solid #eee;
}

#taskList input[type="checkbox"] {
margin-right: 0.75rem;
}

.task-text {
flex: 1;
font-size: 1rem;
}

.task-text.completed {
text-decoration: line-through;
color: #888;
}

.delete-btn {
background: none;
border: none;
color: #dc3545;
font-size: 1.2rem;
cursor: pointer;
padding: 0 0.5rem;
}

JavaScript

<script>
const taskList = document.getElementById('taskList');
const addTaskForm = document.getElementById('addTaskForm');

// Загрузка задач из localStorage
let tasks = JSON.parse(localStorage.getItem('tasks')) || [];
renderTasks();

function renderTasks() {
taskList.innerHTML = '';
tasks.forEach((task, index) => {
const li = document.createElement('li');
li.innerHTML = `
<input type="checkbox" ${task.completed ? 'checked' : ''} data-index="${index}">
<span class="task-text ${task.completed ? 'completed' : ''}">${task.text}</span>
<button class="delete-btn" data-index="${index}">×</button>
`;
taskList.appendChild(li);
});
}

addTaskForm.addEventListener('submit', function(e) {
e.preventDefault();
const input = document.getElementById('newTask');
const text = input.value.trim();
if (text) {
tasks.push({ text, completed: false });
localStorage.setItem('tasks', JSON.stringify(tasks));
renderTasks();
input.value = '';
}
});

taskList.addEventListener('click', function(e) {
if (e.target.matches('input[type="checkbox"]')) {
const index = e.target.dataset.index;
tasks[index].completed = e.target.checked;
localStorage.setItem('tasks', JSON.stringify(tasks));
renderTasks();
}
if (e.target.matches('.delete-btn')) {
const index = e.target.dataset.index;
tasks.splice(index, 1);
localStorage.setItem('tasks', JSON.stringify(tasks));
renderTasks();
}
});
</script>

📌 Внимание
Данные хранятся в localStorage, что подходит для личного использования, но не для многопользовательских систем. Для общего доступа требуется серверное хранилище.


Страница восстановления пароля

Стандартный элемент безопасности, позволяющий пользователю сбросить забытый пароль.

HTML

<form id="resetForm" method="POST" action="reset_password.php">
<h2>Восстановление пароля</h2>
<p>Введите ваш email, и мы вышлем ссылку для сброса пароля.</p>
<div class="form-group">
<label for="resetEmail">Email</label>
<input type="email" id="resetEmail" name="email" required>
</div>
<button type="submit">Отправить ссылку</button>
<p class="form-footer">
<a href="login.php">← Назад к входу</a>
</p>
</form>

CSS

#resetForm {
max-width: 450px;
margin: 2rem auto;
padding: 2rem;
border: 1px solid #ddd;
border-radius: 8px;
background-color: #fafafa;
}

#resetForm h2 {
text-align: center;
margin-bottom: 0.5rem;
}

#resetForm p {
text-align: center;
color: #666;
margin-bottom: 1.5rem;
}

PHP (reset_password.php)

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = filter_var($_POST['email'] ?? '', FILTER_SANITIZE_EMAIL);

if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
die('Некорректный email.');
}

// Проверка существования пользователя (в реальности — запрос к БД)
// if (!userExists($email)) { ... }

// Генерация токена (пример)
$token = bin2hex(random_bytes(32));
// Сохранение токена в БД с временем жизни (например, 1 час)

// Отправка письма (в реальности — через PHPMailer и т.п.)
// $link = "https://example.com/set_new_password.php?token=$token";
// mail($email, "Сброс пароля", "Перейдите по ссылке: $link");

echo '<p style="color: green; text-align: center;">Ссылка для сброса пароля отправлена на ваш email.</p>';
}
?>

⚠️ Предупреждение
Никогда не сообщайте пользователю, существует ли email в системе. Всегда выводите нейтральное сообщение, чтобы избежать перебора учётных записей.


Форма загрузки файла

Загрузка файлов — частая задача в веб-приложениях: аватары, документы, изображения и т.д.

HTML

<form id="uploadForm" method="POST" enctype="multipart/form-data" action="upload.php">
<h2>Загрузка файла</h2>
<div class="form-group">
<label for="fileInput">Выберите файл</label>
<input type="file" id="fileInput" name="uploaded_file" accept=".jpg,.jpeg,.png,.pdf,.doc,.docx" required>
<small class="hint">Поддерживаются: JPG, PNG, PDF, DOC/DOCX (макс. 5 МБ)</small>
</div>
<button type="submit">Загрузить</button>
</form>

CSS

#uploadForm {
max-width: 500px;
margin: 2rem auto;
padding: 2rem;
border: 1px solid #ddd;
border-radius: 8px;
background-color: #fafafa;
}

#uploadForm h2 {
text-align: center;
margin-bottom: 1.5rem;
}

.form-group {
margin-bottom: 1.25rem;
}

.form-group label {
display: block;
margin-bottom: 0.4rem;
font-weight: bold;
}

.form-group input[type="file"] {
width: 100%;
padding: 0.5rem 0;
font-size: 1rem;
}

.hint {
display: block;
margin-top: 0.25rem;
font-size: 0.875rem;
color: #666;
}

PHP (upload.php)

<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!isset($_FILES['uploaded_file'])) {
die('Файл не был отправлен.');
}

$file = $_FILES['uploaded_file'];
$uploadDir = __DIR__ . '/uploads/';
$maxSize = 5 * 1024 * 1024; // 5 МБ
$allowedTypes = ['image/jpeg', 'image/png', 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'];

// Проверка ошибок
if ($file['error'] !== UPLOAD_ERR_OK) {
die('Ошибка при загрузке файла.');
}

// Проверка размера
if ($file['size'] > $maxSize) {
die('Файл слишком большой (макс. 5 МБ).');
}

// Проверка типа
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);

if (!in_array($mimeType, $allowedTypes)) {
die('Недопустимый тип файла.');
}

// Генерация безопасного имени
$extension = pathinfo($file['name'], PATHINFO_EXTENSION);
$safeName = bin2hex(random_bytes(16)) . '.' . strtolower($extension);
$targetPath = $uploadDir . $safeName;

// Создание директории, если её нет
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}

// Перемещение файла
if (move_uploaded_file($file['tmp_name'], $targetPath)) {
echo "Файл успешно загружен! <br>Путь: " . htmlspecialchars($targetPath);
} else {
die('Не удалось сохранить файл.');
}
}
?>

⚠️ Предупреждение
Никогда не используйте оригинальное имя файла напрямую ($_FILES['name']) — это уязвимость. Всегда генерируйте уникальное безопасное имя.


Уведомления (Notifications)

Всплывающие сообщения для информирования пользователя об успехе, ошибках или предупреждениях.

HTML

<!-- Контейнер для уведомлений -->
<div id="notificationContainer"></div>

<!-- Пример вызова -->
<button onclick="showNotification('success', 'Данные успешно сохранены!')">Показать успех</button>
<button onclick="showNotification('error', 'Произошла ошибка при отправке.')">Показать ошибку</button>
<button onclick="showNotification('warning', 'Пароль слишком слабый.')">Показать предупреждение</button>

CSS

#notificationContainer {
position: fixed;
top: 20px;
right: 20px;
z-index: 2000;
max-width: 400px;
}

.notification {
padding: 1rem 1.5rem;
margin-bottom: 0.75rem;
border-radius: 4px;
color: white;
font-weight: bold;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
animation: slideInRight 0.3s ease-out;
display: flex;
justify-content: space-between;
align-items: center;
}

.notification.success { background-color: #28a745; }
.notification.error { background-color: #dc3545; }
.notification.warning { background-color: #ffc107; color: #212529; }

@keyframes slideInRight {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}

.close-notification {
background: none;
border: none;
color: inherit;
font-size: 1.2rem;
cursor: pointer;
padding: 0 0.5rem;
}

JavaScript

<script>
function showNotification(type, message, duration = 5000) {
const container = document.getElementById('notificationContainer');
const notification = document.createElement('div');
notification.className = `notification ${type}`;
notification.innerHTML = `
${message}
<button class="close-notification" aria-label="Закрыть">&times;</button>
`;
container.appendChild(notification);

// Автоматическое скрытие
const timer = setTimeout(() => {
removeNotification(notification);
}, duration);

// Закрытие по клику
notification.querySelector('.close-notification').addEventListener('click', () => {
clearTimeout(timer);
removeNotification(notification);
});
}

function removeNotification(el) {
el.style.animation = 'slideOutRight 0.3s forwards';
setTimeout(() => el.remove(), 300);
}

// Анимация исчезновения
const style = document.createElement('style');
style.textContent = `
@keyframes slideOutRight {
to {
transform: translateX(100%);
opacity: 0;
}
}
`;
document.head.appendChild(style);
</script>

💡 Совет
Для доступности добавьте role="alert" к уведомлениям об ошибках и role="status" — к информационным.


Страница «404 — Не найдено»

Стандартная страница ошибки, отображаемая при обращении к несуществующему URL.

HTML

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Страница не найдена</title>
<style>
body {
font-family: Arial, sans-serif;
background: #f8f9fa;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
color: #333;
}
.error-page {
text-align: center;
max-width: 600px;
padding: 2rem;
}
.error-code {
font-size: 6rem;
font-weight: bold;
color: #dc3545;
margin: 0 0 1rem 0;
}
.error-message {
font-size: 1.5rem;
margin: 0 0 2rem 0;
color: #666;
}
.btn-home {
display: inline-block;
padding: 0.6rem 1.5rem;
background: #007bff;
color: white;
text-decoration: none;
border-radius: 4px;
font-size: 1.1rem;
transition: background 0.2s;
}
.btn-home:hover {
background: #0056b3;
}
</style>
</head>
<body>
<div class="error-page">
<div class="error-code">404</div>
<p class="error-message">Страница не найдена</p>
<p>К сожалению, запрашиваемая вами страница не существует.</p>
<a href="/" class="btn-home">На главную</a>
</div>
</body>
</html>

📌 Важно
Сервер должен возвращать HTTP-статус 404, а не просто показывать страницу с таким текстом. Иначе поисковые системы будут считать её валидной.


Освоение главы0%